import {emit} from "./runner-factory";
import {} from "reflect-metadata"

interface FuncOption {
  descName: string,
  beforeFunc?: string|Function,
  beforeParams?: any,
  afterFunc?: string|Function,
  afterParams?: any[],
  withSelf: boolean,
}

function baseFunc(options: Partial<FuncOption>) {
  return function (target: any, name:string, desc: PropertyDescriptor) {
    if (!options.descName) throw new Error(`descName Can't not be empty!`)

    const method = desc[options.descName];
    if (!method) throw new Error(`Wrong '${method}' used on ${name}`)

    desc[options.descName] = async function(...args) {
      if (options.beforeFunc) {
        const func = typeof options.beforeFunc == 'string' ? this[options.beforeFunc] : options.beforeFunc;
        if (options.withSelf) {
          options.beforeParams = [this, ...args, ...options.afterParams ?? []]
        }
        await func.apply(this, options.beforeParams);
      }
      await method.apply(this, args);

      if (options.afterFunc) {
        const func = typeof options.afterFunc == 'string' ? this[options.afterFunc] : options.afterFunc;
        let params = options.afterParams ?? [];
        if (options.withSelf) {
          params = [this, ...args, ...params]
        }
        await func.apply(this, params);
      }
    }
  }
}

export function SetCall(funcName, ...params) {
  return baseFunc({
    descName: 'set',
    afterFunc: funcName,
    afterParams: params,
  })
}

export function EmitAfter(func:string, ...params: any) {
  return baseFunc({
    descName: 'value',
    afterFunc: async function(...args) {await emit(func, ...args)},
    afterParams: params,
    withSelf: true,
  });
}

// export async function EmitBefore(func: string, ...params: any) {
//   return async function (target: any, name:string, desc: PropertyDescriptor) {
//     return baseFunc(...arguments, )
//     const method = desc.value;
//     desc.value = async function(...args) {
//       await emit(func, ...params);
//       await method.apply(this, args);
//     }
//   }
// }

export function LogExecTime() {
  return function (target: any, name:string, desc: PropertyDescriptor) {

    const method = desc.value;
    desc.value = async function(...args) {
      const now = new Date().getTime();
      await method.apply(this, args);
      console.log(`${name} cost ${new Date().getTime() - now}ms`)
    }
  }
}

export function funcProxy(func:Function) {
  return new Proxy(func, {
    apply(target: Function, thisArg: any, argArray: any[]): any {
      console.log(target, thisArg, argArray);
    }
  });
}
